/*-
 * Copyright 2012 Guram Dukashvili
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
//---------------------------------------------------------------------------
#include "sys.h"
#include "rand.h"
//---------------------------------------------------------------------------
struct rand * rand_new(void)
{
  struct rand * rnd = salloc(SIZEOF_STRUCT_RAND,NULL);
  if( rnd != NULL )
    rand_init(rnd);
  return rnd;
}
//---------------------------------------------------------------------------
void rand_delete(struct rand * rnd)
{
  sfree(rnd);
}
//---------------------------------------------------------------------------
void rand_init(struct rand * rnd)
{
  rnd->alpha = 8;
  rnd->count = rnd->n = UINT32_C(1) << rnd->alpha;
  rnd->rsl = (uint32_t *) (rnd + 1);
  rnd->mem = rnd->rsl + rnd->n;
}
//---------------------------------------------------------------------------
struct rngstep_refdata {
  uint32_t x, y;

  uint32_t * mm;
  uint32_t * r;

  uint32_t a;
  uint32_t b;

  uint32_t * m;
  uint32_t * m2;
  uint32_t * mend;
};
//---------------------------------------------------------------------------
static __inline__ uint32_t ind(
  const struct rand * rnd,uint32_t * mm,uint32_t x)
{
  return *(uint32_t *) ((uint8_t *) mm + (x & ((rnd->n - 1) << 2)));
}
//---------------------------------------------------------------------------
static __inline__ void rngstep(
  const struct rand * rnd,
  uint32_t mix,
  struct rngstep_refdata * r)
{
  r->x = *r->m;
  r->a = (r->a ^ mix) + *(r->m2++);
  *(r->m++) = r->y = ind(rnd,r->mm,r->x) + r->a + r->b;
  *(r->r++) = r->b = ind(rnd,r->mm,r->y >> rnd->alpha) + r->x;
}
//---------------------------------------------------------------------------
static __inline__ void isaac(struct rand * rnd)
{
  struct rngstep_refdata r;

  r.mm = rnd->mem;
  r.r  = rnd->rsl;

  r.a = rnd->a;
  r.b = rnd->b + (++rnd->c);

  r.m    = r.mm;
  r.m2   = r.m + (rnd->n / 2);
  r.mend = r.m2;

  for( ; r.m < r.mend; ){
    rngstep(rnd,(r.a << 13),&r);
    rngstep(rnd,(r.a >>  6),&r);
    rngstep(rnd,(r.a <<  2),&r);
    rngstep(rnd,(r.a >> 16),&r);
  }

  r.m2 = r.mm;

  for( ; r.m2 < r.mend; ){
    rngstep(rnd,(r.a << 13),&r);
    rngstep(rnd,(r.a >>  6),&r);
    rngstep(rnd,(r.a <<  2),&r);
    rngstep(rnd,(r.a >> 16),&r);
  }

  rnd->b = r.b;
  rnd->a = r.a;
}
//---------------------------------------------------------------------------
uint32_t rand_next(struct rand * rnd)
{
  if( rnd->count == 0 ){
    isaac(rnd);
    rnd->count = rnd->n;
  }
  return rnd->rsl[--rnd->count];
}
//---------------------------------------------------------------------------
struct shiffle_refdata {
  uint32_t a, b, c, d, e, f, g, h;
};
//---------------------------------------------------------------------------
static __inline__ void shuffle(struct shiffle_refdata * r)
{
  r->a ^= r->b << 11; r->d += r->a; r->b += r->c;
  r->b ^= r->c >>  2; r->e += r->b; r->c += r->d;
  r->c ^= r->d <<  8; r->f += r->c; r->d += r->e;
  r->d ^= r->e >> 16; r->g += r->d; r->e += r->f;
  r->e ^= r->f << 10; r->h += r->e; r->f += r->g;
  r->f ^= r->g >>  4; r->a += r->f; r->g += r->h;
  r->g ^= r->h <<  8; r->b += r->g; r->h += r->a;
  r->h ^= r->a >>  9; r->c += r->h; r->a += r->b;
}
//---------------------------------------------------------------------------
static __inline__ void init(struct rand * rnd,bool use_seed)
{
  struct shiffle_refdata d;
  uint32_t * m, * r, i;

  d.a = d.b = d.c = d.d = d.e = d.f = d.g = d.h = UINT32_C(0x9E3779B9);

  m = (rnd->mem);
  r = (rnd->rsl);

  if( !use_seed ){
    rnd->a = 0;
    rnd->b = 0;
    rnd->c = 0;
  }

  for( i = 0; i < 4; i++ )
    shuffle(&d);

  if( use_seed ){
    // initialize using the contents of r[] as the seed

     for( i = 0; i < rnd->n; i += 8 ){
       d.a += r[i];     d.b += r[i + 1]; d.c += r[i + 2]; d.d += r[i + 3];
       d.e += r[i + 4]; d.f += r[i + 5]; d.g += r[i + 6]; d.h += r[i + 7];

       shuffle(&d);

       m[i]     = d.a; m[i + 1] = d.b; m[i + 2] = d.c; m[i + 3] = d.d;
       m[i + 4] = d.e; m[i + 5] = d.f; m[i + 6] = d.g; m[i + 7] = d.h;
     }

    //do a second pass to make all of the seed affect all of m

     for( i = 0; i < rnd->n; i += 8 ){
       d.a += m[i];     d.b += m[i + 1]; d.c += m[i + 2]; d.d += m[i + 3];
       d.e += m[i + 4]; d.f += m[i + 5]; d.g += m[i + 6]; d.h += m[i + 7];

       shuffle(&d);

       m[i]     = d.a; m[i + 1] = d.b; m[i + 2] = d.c; m[i + 3] = d.d;
       m[i + 4] = d.e; m[i + 5] = d.f; m[i + 6] = d.g; m[i + 7] = d.h;
     }
  }
  else {
    // fill in mm[] with messy stuff
    for( i = 0; i < rnd->n; i += 8 ){
      shuffle(&d);

      m[i]     = d.a; m[i + 1] = d.b; m[i + 2] = d.c; m[i + 3] = d.d;
      m[i + 4] = d.e; m[i + 5] = d.f; m[i + 6] = d.g; m[i + 7] = d.h;
    }
  }

  isaac(rnd);              // fill in the first set of results
  rnd->count = rnd->n;    // prepare to use the first set of results
}
//---------------------------------------------------------------------------
void rand_srand(
  struct rand * rnd,uint32_t a,uint32_t b,uint32_t c)
{
  uint32_t i;

  for( i = 0; i < rnd->n; i++ )
    rnd->rsl[i] = 0;

  rnd->a = a;
  rnd->b = b;
  rnd->c = c;

  init(rnd,true);
}
//---------------------------------------------------------------------------
